/* Core syscall procedures */

#include <clock/clock.h>
#include <cspace/cspace.h>
#include <fcntl.h>
#include <sel4/sel4.h>
#include <sos.h>
#include <string.h>
#include <libut.h>

#include "macros.h"
#include "pagetable.h"
#include "process.h"
#include "syscall.h"
#include "task.h"
#include "vfs.h"

#include "debug.h"
#define verbose SOS_DEBUG_SYSCALL
#include <sys/debug.h>

extern sos_pcb tty_test_pcb;
extern sos_vfs *vfs;

void sos_syscall_open(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    seL4_MessageInfo_t reply;
    seL4_IPCBuffer *ipc;
    int fd;
    int len;

    int proc_len = (int) seL4_GetMR(2);

    fmode_t mode = (fmode_t) seL4_GetMR(1);

    pcb->reply_cap = reply_cap;

    ipc = seL4_GetIPCBuffer();

    // This is an unsafe print, but debugs should be off in production
    dprintf(5, "sos_syscall_open: %.128s\n", (char *) (ipc->msg + 3));

    len = strnlen((char *) (ipc->msg + 3), MIN(SOS_PATH_MAX, proc_len));

    dprintf(3, "len = %d, proc_len = %d\n", len, proc_len);

    if (len == SOS_PATH_MAX || len == 0 || len != proc_len ||
               (mode != O_RDONLY && mode != O_WRONLY && mode != O_RDWR)) {
        // Filename too long / short / badperms
        sos_syscall_open_reply(pcb, -1, SOS_VFS_INVAL);
        return;
    }

    // Force a null byte. Note that SOS_PATH_MAX is well inside the IPC buffer
    *(((char *) (ipc->msg + 3)) + len) = '\0';

    dprintf(5, "sos_syscall_open: %.128s\n", (char *) (ipc->msg + 3));

    err = sos_vfs_open(pcb, vfs, (char*) (ipc->msg + 3), (fmode_t) ipc->msg[1]);

    if (err) {
        // Wake up client with an error code
        dprintf(3, "sos_vfs_open returned an error (%d)\n", err);
        sos_syscall_open_reply(pcb, -1, err);
    }
}

void sos_syscall_open_reply(sos_pcb *pcb, int proc_fd, int err) {
    if (pcb->reply_cap == 0) return;

    dprintf(6, "sos_syscall_open_reply: calling back (%d)\n", err);

    dprintf(7, "pcb->pid = %d, pcb->reply_cap = %p\n",
            pcb->pid, pcb->reply_cap);

    if (err) {
        sos_vfs_close(pcb, vfs, proc_fd);
    }

    seL4_MessageInfo_t reply;
    reply = seL4_MessageInfo_new(0, 0, 0, 2);
    seL4_SetMR(0, proc_fd);
    seL4_SetMR(1, err);
    seL4_Send((pcb->reply_cap), reply);
    cspace_free_slot(cur_cspace, (pcb->reply_cap));
    pcb->reply_cap = 0;
    return;
}

/* Synchronous */
void sos_syscall_close(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    seL4_MessageInfo_t reply;

    dprintf(5, "sos_syscall_close: FD %d\n", seL4_GetMR(1));

    err = sos_vfs_close(pcb, vfs, seL4_GetMR(1));
    reply = seL4_MessageInfo_new(0, 0, 0, 1);
    seL4_SetMR(0, err);
    seL4_Send(reply_cap, reply);
    cspace_free_slot(cur_cspace, reply_cap);
}

void sos_syscall_write(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;

    int proc_fd = (int) seL4_GetMR(1);
    seL4_Word proc_vaddr = seL4_GetMR(2);
    size_t nbyte = seL4_GetMR(3);

    pcb->reply_cap = reply_cap;

    dprintf(5, "sos_syscall_write: PID %d  FD %d %dB\n",\
            pcb->pid, proc_fd, (int) nbyte);

    if (proc_fd < 0) {
       sos_syscall_write_reply(pcb, SOS_VFS_INVAL, 0);
       return;
    }

    err = sos_vfs_write(pcb, vfs, proc_fd, proc_vaddr, nbyte);
    if (err) dprintf(3, "sos_vfs_write returned an error (%d)\n", err);
}

void sos_syscall_write_reply(sos_pcb *pcb, int err, seL4_Word sent) {
    if (pcb->reply_cap == 0) return;

    seL4_MessageInfo_t reply;
    dprintf(6, "syscall_write_reply: Wrote %d bytes. Calling back...(err = %d)\n",
            sent, err);
    seL4_SetMR(0, (seL4_Word) sent);
    seL4_SetMR(1, (seL4_Word) err);
    reply = seL4_MessageInfo_new(0, 0, 0, 2);
    seL4_Send(pcb->reply_cap, reply);
    cspace_free_slot(cur_cspace, pcb->reply_cap);
    pcb->reply_cap = 0;
}

void sos_syscall_read(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;

    int proc_fd = (int) seL4_GetMR(1);
    seL4_Word proc_vaddr = seL4_GetMR(2);
    size_t nbyte = seL4_GetMR(3);

    pcb->reply_cap = reply_cap;

    dprintf(5, "sos_syscall_read: FD %d %d bytes\n",
            proc_fd, nbyte);

    if (proc_fd < 0) {
        sos_syscall_read_reply(pcb, SOS_VFS_INVAL, 0);
        return;
    }

    err = sos_vfs_read(pcb, vfs, proc_fd, proc_vaddr, nbyte);

    if (err) dprintf(5, "sos_vfs_read returned an error (%d)\n", err);
}

void sos_syscall_read_reply(sos_pcb *pcb, int err, seL4_Word sent) {
    if (pcb->reply_cap == 0) return;
    seL4_MessageInfo_t reply;
    // Wake up the calling process
    dprintf(6, "syscall_read: Read %d bytes. Calling back...\n", sent);
    seL4_SetMR(0, sent);
    seL4_SetMR(1, err);
    reply = seL4_MessageInfo_new(0, 0, 0, 2);
    seL4_Send(pcb->reply_cap, reply);
    cspace_free_slot(cur_cspace, pcb->reply_cap);
    pcb->reply_cap = 0;
}

void sos_syscall_stat(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    seL4_MessageInfo_t reply;
    seL4_IPCBuffer *ipc;

    pcb->reply_cap = reply_cap;

    ipc = seL4_GetIPCBuffer();

    int proc_len = (int) seL4_GetMR(2);
    int len = strnlen((char *) (ipc->msg + 3), MIN(SOS_PATH_MAX, proc_len));
    if (len == SOS_PATH_MAX || len != proc_len) {
        sos_stat_t sb = { 0 };
        sos_syscall_stat_reply(pcb, SOS_VFS_INVAL, sb);
        return;
    }

    dprintf(5, "sos_syscall_stat called\n");
    err = sos_vfs_stat(pcb, vfs, (char*) (ipc->msg + 3));
    if (err) dprintf(5, "sos_vfs_stat returned an error (%d)\n", err);
}


void sos_syscall_stat_reply(sos_pcb *pcb, int err, sos_stat_t stat_buf) {
    if (pcb->reply_cap == 0) return;
    seL4_MessageInfo_t reply;
    reply = seL4_MessageInfo_new(0, 0, 0, 6);
    seL4_SetMR(0, err);
    seL4_SetMR(1, stat_buf.st_type);
    seL4_SetMR(2, stat_buf.st_fmode);
    seL4_SetMR(3, stat_buf.st_size);
    seL4_SetMR(4, (seL4_Word) stat_buf.st_ctime);
    seL4_SetMR(5, (seL4_Word) stat_buf.st_atime);
    seL4_Send(pcb->reply_cap, reply);
    cspace_free_slot(cur_cspace, pcb->reply_cap);
    pcb->reply_cap = 0;
}

void sos_syscall_getdirent(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    seL4_MessageInfo_t reply;

    dprintf(5, "sos_syscall_getdirent called\n");
    pcb->reply_cap = reply_cap;

    err = sos_vfs_getdirent(pcb, vfs, (int) seL4_GetMR(1), seL4_GetMR(2));
    if (err) dprintf(5, "sos_vfs_getdirent returned an error (%d)\n");
}

void sos_syscall_getdirent_reply(sos_pcb *pcb, char *name, size_t nbyte,
                                 int err) {
    if (pcb->reply_cap == 0) return;
    int len;
    seL4_MessageInfo_t reply;
    seL4_IPCBuffer *ipc;

    dprintf(6, "sos_getdirent_reply: calling back\n");

    ipc = seL4_GetIPCBuffer();

    seL4_SetMR(0, err);

    int nlen = MIN(nbyte, SOS_PATH_MAX);
    if (nlen != nbyte) err = SOS_VFS_INVAL;

    if (!err) {
        seL4_SetMR(1, nlen);
        strncpy((char *) (ipc->msg + 2), name, nlen);
        if (nlen > 0) {
            // Set null byte - we're truncating the path
            ((char *) (ipc->msg + 2))[nlen - 1] = '\0';
        }
        // Include the length in our IPC message
        len = 2 + DIVROUND(nlen, 4);
    } else {
        len = 1;
    }

    reply = seL4_MessageInfo_new(0, 0, 0, len);
    seL4_Send(pcb->reply_cap, reply);
    cspace_free_slot(cur_cspace, pcb->reply_cap);
    pcb->reply_cap = 0;
}

static void _usleep_callback(uint32_t id, void *data) {
    dprintf(6, "sos_syscall_usleep_cb: calling back\n");
    sos_pcb *pcb = (sos_pcb *) data;
    pcb->sleep_timer = 0;
    seL4_MessageInfo_t reply = seL4_MessageInfo_new(0, 0, 0, 0);
    seL4_Send(pcb->reply_cap, reply);
    cspace_free_slot(cur_cspace, pcb->reply_cap);
    pcb->reply_cap = 0;
}

void sos_syscall_usleep(sos_pcb *pcb, seL4_Word reply_cap) {
    dprintf(5, "sos_syscall_usleep: %dus\n", (int) seL4_GetMR(1));
    int msec = (int) seL4_GetMR(1);
    pcb->reply_cap = reply_cap;
    if (msec <= 0) {
        _usleep_callback(0, (void *) pcb);
    } else {
        pcb->sleep_timer = register_timer((uint64_t) msec,
                                          &_usleep_callback, (void *) pcb);
    }
}

void sos_syscall_time_stamp(sos_pcb *pcb, seL4_Word reply_cap) {
    seL4_IPCBuffer *ipc;
    seL4_MessageInfo_t reply;
    timestamp_t ts;

    dprintf(5, "sos_syscall_time_stamp\n");

    ipc = seL4_GetIPCBuffer();
    reply = seL4_MessageInfo_new(0, 0, 0, 2);
    ts = time_stamp();

    // Timestamp is two words
    memcpy((void *) ipc->msg, &ts, sizeof(timestamp_t));
    seL4_Send(reply_cap, reply);
    cspace_free_slot(cur_cspace, reply_cap);
}

/* totally synchronous */
void sos_syscall_brk(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    seL4_MessageInfo_t reply;
    seL4_Word new_brk;

    new_brk = seL4_GetMR(1);

    dprintf(5, "sos_syscall_brk: attempt to set PID %d brk to %p\n",
            pcb->pid, new_brk);
    reply = seL4_MessageInfo_new(0, 0, 0, 2);

    err = sos_brk_move(pcb, new_brk);
    dprintf(5, "sos_syscall_brk: error code %d\n", err);

    seL4_SetMR(0, new_brk);
    seL4_SetMR(1, err);
    seL4_Send(reply_cap, reply);
    cspace_free_slot(cur_cspace, reply_cap);
    pcb->reply_cap = 0;
    dprintf(6, "sos_syscall_brk: finished\n");
}

void sos_syscall_pid(sos_pcb *pcb, seL4_Word reply_cap) {
    dprintf(5, "sos_syscall_pid: PID %d\n", pcb->pid);

    seL4_MessageInfo_t reply = seL4_MessageInfo_new(0, 0, 0, 1);

    seL4_SetMR(0, (seL4_Word) pcb->pid);
    seL4_Send(reply_cap, reply);
    cspace_free_slot(cur_cspace, reply_cap);
}

static void _proc_delete_other(int err, void *cookie) {
    sos_pcb *pcb = (sos_pcb *) cookie;
    dprintf(6, "syscall_proc_delete_other: calling process back (%d)\n", err);
    seL4_Word reply_cap = pcb->reply_cap;
    seL4_MessageInfo_t reply = seL4_MessageInfo_new(0, 0, 0, 1);
    seL4_SetMR(0, (seL4_Word) err);
    seL4_Send(reply_cap, reply);
    cspace_free_slot(cur_cspace, reply_cap);
    pcb->reply_cap = 0;
    dprintf(7, "syscall_proc_delete_other: send complete\n");
}

void sos_syscall_process_delete(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    int pid = (int) seL4_GetMR(1);
    sos_pcb *target;

    dprintf(5, "sos_syscall_proc_delete: PID %d kills PID %d\n", pcb->pid, pid);

    pcb->reply_cap = reply_cap;

    if (pid == 0 || pid == pcb->pid) {
        // Delete self
        cspace_free_slot(cur_cspace, reply_cap);
        err = process_delete(pcb, NULL, NULL);
    } else {
        err = lookup_pcb(pid, &target);
        if (err != SOS_PROCESS_SUCCESS) {
            dprintf(5, "process_delete: invalid PID (%d)\n", pid);
            _proc_delete_other(SOS_PROCESS_NOENT, (void *) pcb);
            return;
        } else {
            err = process_delete(target,
                                 &_proc_delete_other, (void *) pcb);
        }
    }

    if (err) {
        dprintf(5, "process_delete: could not delete PID %d\n", target->pid);
    }
}

static void process_create_reply(int err, int pid, void *cookie) {
    sos_pcb *pcb = (sos_pcb *) cookie;
    if (pcb->reply_cap == 0) return;
    if (pcb->abort) {
        dprintf(6, "syscall_process_create_reply: cleaning up aborted PID\n");
        free(pcb);
        return;
    }

    if (!err) {
        // Need to cache the new PID to avoid races on process_wait
        sos_pcb *new = NULL;
        lookup_pcb(pid, &new);
        process_register_child(pcb, new, pid);
    }

    seL4_Word reply_cap = pcb->reply_cap;
    seL4_MessageInfo_t reply = seL4_MessageInfo_new(0, 0, 0, 2);
    seL4_SetMR(0, (seL4_Word) pid);
    seL4_SetMR(1, (seL4_Word) err);
    seL4_Send(reply_cap, reply);
    cspace_free_slot(cur_cspace, reply_cap);
    pcb->reply_cap = 0;
}

void sos_syscall_process_create(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    seL4_MessageInfo_t reply;
    seL4_IPCBuffer *ipc;
    int fd;

    int proc_len = (int) seL4_GetMR(1);

    ipc = seL4_GetIPCBuffer();
    pcb->reply_cap = reply_cap;


    int len = strnlen((char *) (ipc->msg + 2), MIN(SOS_PATH_MAX, proc_len));
    if (len >= SOS_PATH_MAX || len == 0 || len != proc_len) {
        // Filename too long - inval
        dprintf(6, "syscall_proc_create: invalid path (length %d)\n", len);
        process_create_reply(SOS_VFS_INVAL, -1, (void *) pcb);
        return;
    }

    // Force a null byte
    *(((char *) (ipc->msg + 2)) + len) = '\0';

    dprintf(5, "sos_syscall_proc_create: %.128s\n", (char *) (ipc->msg + 2));

    err = process_create(ipc->msg + 2,
                         &process_create_reply, (void *) pcb);
    if (err) {
        dprintf(5, "syscall_process_create: process_create returned %d\n", err);
    }
}

void sos_syscall_process_wait(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    int target_pid = (int) seL4_GetMR(1);
    seL4_MessageInfo_t reply;

    dprintf(5, "sos_syscall_process_wait: PID %d waiting on %d\n",
            pcb->pid, target_pid);

    // Stash reply cap in PCB
    pcb->reply_cap = reply_cap;

    err = process_wait(pcb, target_pid);

    if (err) {
        dprintf(6, "sos_syscall_process_wait: PID %d failed to wait on %d\n",
                pcb->pid, target_pid);
        reply = seL4_MessageInfo_new(0, 0, 0, 2);
        seL4_SetMR(0, (seL4_Word) err);
        seL4_Send(pcb->reply_cap, reply);
        cspace_free_slot(cur_cspace, reply_cap);
        pcb->reply_cap = 0;
    }
}

void sos_syscall_process_status(sos_pcb *pcb, seL4_Word reply_cap) {
    int err;
    seL4_IPCBuffer *ipc;

    int proc = (int) seL4_GetMR(1);

    pcb->reply_cap = reply_cap;
    process_status(pcb, proc);

    return;
}

void sos_syscall_process_status_reply(sos_pcb *pcb, sos_pcb *info) {
    seL4_MessageInfo_t reply = seL4_MessageInfo_new(0, 0, 0, 14);
    if (info == NULL) {
        // Error out
        seL4_SetMR(0, 1);
    } else {
        seL4_SetMR(0, 0);
        seL4_IPCBuffer *ipc = seL4_GetIPCBuffer();
        seL4_SetMR(1, (seL4_Word) info->pid);
        seL4_SetMR(2, 0); // size goes here
        memcpy(&(ipc->msg[3]), &(info->stime), sizeof(info->stime));
        strncpy(&(ipc->msg[5]), info->app_name, N_NAME);
    }
    dprintf(7, "pcb = %p\treply_cap = %p\n", pcb, pcb->reply_cap);
    seL4_Send(pcb->reply_cap, reply);
    cspace_free_slot(cur_cspace, pcb->reply_cap);
    pcb->reply_cap = 0;
    dprintf(7, "process_status_reply: replying...\n");
    return;
}
